package oss

import (
	"bytes"
	"crypto/md5"
	"fmt"
	"gitee.com/wuzheng0709/backend-gopkg/infrastructure/config"
	"gitee.com/wuzheng0709/backend-gopkg/infrastructure/pkg/gin/log"
	"github.com/aliyun/aliyun-oss-go-sdk/oss"
	"io"
	"mime/multipart"
	"net/url"
	"os"
	"strconv"
	"strings"
)

// 定义进度条监听器。
type OssProgressListener struct {
}

// 定义进度变更事件处理函数。
func (listener *OssProgressListener) ProgressChanged(event *oss.ProgressEvent) {
	switch event.EventType {
	case oss.TransferStartedEvent:
		fmt.Printf("Transfer Started, ConsumedBytes: %d, TotalBytes %d.\n",
			event.ConsumedBytes, event.TotalBytes)
	case oss.TransferDataEvent:
		fmt.Printf("\rTransfer Data, ConsumedBytes: %d, TotalBytes %d, %d%%.",
			event.ConsumedBytes, event.TotalBytes, event.ConsumedBytes*100/event.TotalBytes)
	case oss.TransferCompletedEvent:
		fmt.Printf("\nTransfer Completed, ConsumedBytes: %d, TotalBytes %d.\n",
			event.ConsumedBytes, event.TotalBytes)
	case oss.TransferFailedEvent:
		fmt.Printf("\nTransfer Failed, ConsumedBytes: %d, TotalBytes %d.\n",
			event.ConsumedBytes, event.TotalBytes)
	default:
	}
}

func handleError(err error) {
	fmt.Println("Error:", err)
	os.Exit(1)
}

func GetVersion() (version string) {
	return oss.Version
}

func NewOss(endpoint string) (client *oss.Client, err error) {
	// Endpoint以杭州为例，其它Region请按实际情况填写。
	// 阿里云主账号AccessKey拥有所有API的访问权限，风险很高。强烈建议您创建并使用RAM账号进行API访问或日常运维，请登录 https://ram.console.aliyun.com 创建RAM账号。
	accessKeyId := config.C.Oss.AccessKeyId
	accessKeySecret := config.C.Oss.AccessKeySecret
	// 创建OSSClient实例。
	client, err = oss.New(endpoint, accessKeyId, accessKeySecret)
	return
}

func UploadAvatarFile(endpoint, fileName, bucketName, uploaddir string) (resultfile string, err error) {
	resultfile = ""
	// 创建OSSClient实例。
	client, err := NewOss(endpoint)
	// 获取存储空间。
	bucket, err := client.Bucket(bucketName)
	if err != nil {
		return
	}
	var objectName string
	if uploaddir != "" {
		objectName = uploaddir + "/" + fileName //完整的oss路径
	} else {
		objectName = fileName
	}
	// 上传文件流。
	err = bucket.PutObjectFromFile(objectName, "uploadfile/"+objectName)
	if err != nil {
		return
	}
	resultfile = url.QueryEscape(uploaddir) + "/" + url.QueryEscape(fileName)
	return
}
func SaveFileMd5ByFileReader(f *multipart.FileHeader) (md5Str, LocalfilePath string, err error) {
	fileReader, err := f.Open()
	if err != nil {
		return
	}
	md5h := md5.New()
	io.Copy(md5h, fileReader)
	md5Str = fmt.Sprintf("%x", md5h.Sum(nil))

	uploaddir := md5Str
	objectName, err := SaveFile(f.Filename, uploaddir, fileReader)
	Localfilepath := "uploadfile/" + objectName
	return md5Str, Localfilepath, err
}
func SaveFile(fileName, uploaddir string, fileReader multipart.File) (objectName string, err error) {

	if uploaddir != "" {
		objectName = uploaddir + "/" + fileName //完整的oss路径
	} else {
		objectName = fileName
	}
	// <yourObjectName>上传文件到OSS时需要指定包含文件后缀在内的完整路径，例如abc/efg/123.jpg。
	log.Info("object name:", objectName)
	fileReader.Seek(0, io.SeekStart)
	os.MkdirAll("uploadfile/"+uploaddir, 0777)
	f, err := os.OpenFile("uploadfile/"+objectName, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
	log.Info(err, "================")
	if err != nil {
		return objectName, err
	}
	defer f.Close()
	io.Copy(f, fileReader)
	defer fileReader.Close()
	return objectName, err
}

// / 解析多个文件上传中，每个具体的文件的信息
type FileHeader struct {
	ContentDisposition string
	Name               string
	FileName           string ///< 文件名
	ContentType        string
	ContentLength      int64
}

// / 解析描述文件信息的头部
// / @return FileHeader 文件名等信息的结构体
// / @return bool 解析成功还是失败
func ParseFileHeader(h []byte) (FileHeader, bool) {
	arr := bytes.Split(h, []byte("\r\n"))
	var out_header FileHeader
	out_header.ContentLength = -1
	const (
		CONTENT_DISPOSITION = "Content-Disposition: "
		NAME                = "name=\""
		FILENAME            = "filename=\""
		CONTENT_TYPE        = "Content-Type: "
		CONTENT_LENGTH      = "Content-Length: "
	)
	for _, item := range arr {
		if bytes.HasPrefix(item, []byte(CONTENT_DISPOSITION)) {
			l := len(CONTENT_DISPOSITION)
			arr1 := bytes.Split(item[l:], []byte("; "))
			out_header.ContentDisposition = string(arr1[0])
			if bytes.HasPrefix(arr1[1], []byte(NAME)) {
				out_header.Name = string(arr1[1][len(NAME) : len(arr1[1])-1])
			}
			l = len(arr1[2])
			if bytes.HasPrefix(arr1[2], []byte(FILENAME)) && arr1[2][l-1] == 0x22 {
				out_header.FileName = string(arr1[2][len(FILENAME) : l-1])
			}
		} else if bytes.HasPrefix(item, []byte(CONTENT_TYPE)) {
			l := len(CONTENT_TYPE)
			out_header.ContentType = string(item[l:])
		} else if bytes.HasPrefix(item, []byte(CONTENT_LENGTH)) {
			l := len(CONTENT_LENGTH)
			s := string(item[l:])
			content_length, err := strconv.ParseInt(s, 10, 64)
			if err != nil {
				log.Info("content length error:%s", string(item))
				return out_header, false
			} else {
				out_header.ContentLength = content_length
			}
		} else {
			log.Info("unknown:%s\n", string(item))
		}
	}
	if len(out_header.FileName) == 0 {
		return out_header, false
	}
	return out_header, true
}

// / 从流中一直读到文件的末位
// / @return []byte 没有写到文件且又属于下一个文件的数据
// / @return bool 是否已经读到流的末位了
// / @return error 是否发生错误
func ReadToBoundary(boundary []byte, stream io.ReadCloser, target io.WriteCloser) ([]byte, bool, error) {
	read_data := make([]byte, 1024*8)
	read_data_len := 0
	buf := make([]byte, 1024*4)
	b_len := len(boundary)
	reach_end := false
	for !reach_end {
		read_len, err := stream.Read(buf)
		if err != nil {
			if err != io.EOF && read_len <= 0 {
				return nil, true, err
			}
			reach_end = true
		}
		//todo: 下面这一句很蠢，值得优化
		copy(read_data[read_data_len:], buf[:read_len]) //追加到另一块buffer，仅仅只是为了搜索方便
		read_data_len += read_len
		if read_data_len < b_len+4 {
			continue
		}
		loc := bytes.Index(read_data[:read_data_len], boundary)
		if loc >= 0 {
			//找到了结束位置
			target.Write(read_data[:loc-4])
			return read_data[loc:read_data_len], reach_end, nil
		}
		target.Write(read_data[:read_data_len-b_len-4])
		copy(read_data[0:], read_data[read_data_len-b_len-4:])
		read_data_len = b_len + 4
	}
	target.Write(read_data[:read_data_len])
	return nil, reach_end, nil
}
func UploadFile(endpoint string, fileName, md5, localFilePath string, bucketname string) (file string, err error) {
	if localFilePath == "" {
		return
	}

	file, err = UploadFileSrc(endpoint, fileName, bucketname, md5, localFilePath)
	if err != nil {
		return
	} else {
		os.Remove(localFilePath)
	}
	return
}
func UploadFileSrc(endpoint, fileName, bucketName, md5, LocalFilePath string) (resultfile string, err error) {
	resultfile = ""
	// 创建OSSClient实例。
	client, err := NewOss(endpoint)
	// 获取存储空间。
	var objectName = md5 + "/" + fileName
	log.Info(objectName)
	log.Info(LocalFilePath)
	bucket, err := client.Bucket(bucketName)
	// 上传文件流。
	err = bucket.PutObjectFromFile(objectName, LocalFilePath, oss.Progress(&OssProgressListener{}))
	if err != nil {
		return
	}
	resultfile = md5 + "/" + url.QueryEscape(fileName)
	return
}

func PutObjectSrc(endpoint, objectName, bucketName, contxttStr string) (err error) {
	// 创建OSSClient实例。
	client, err := NewOss(endpoint)
	// 获取存储空间。
	log.Info(objectName)
	bucket, err := client.Bucket(bucketName)
	// 上传文件流。
	err = bucket.PutObject(objectName, strings.NewReader(contxttStr), oss.Progress(&OssProgressListener{}))
	if err != nil {
		return
	}
	return
}

func UploadLocalFile(endpoint, fileName, bucketName, uploaddir string) (resultfile string, err error) {
	resultfile = ""
	// 创建OSSClient实例。
	client, err := NewOss(endpoint)
	// 获取存储空间。
	bucket, err := client.Bucket(bucketName)
	if err != nil {
		return
	}
	// 上传文件流。
	log.Info("object name:", uploaddir+"/"+fileName)
	err = bucket.PutObjectFromFile(uploaddir+"/"+fileName, uploaddir+"/"+fileName, oss.Progress(&OssProgressListener{}))
	if err != nil {

		return
	}
	resultfile = uploaddir + "/" + url.QueryEscape(fileName)
	return
}
func SetObjectMeta(endpoint, bucketName, obj, filename string) {
	client, err := NewOss(endpoint)
	// 获取存储空间。
	bucket, err := client.Bucket(bucketName)
	if err != nil {
		return
	}
	bucket.SetObjectMeta(obj, oss.ContentDisposition(fmt.Sprintf("attachment; filename=%v", filename)))
}
func BuildSign(endpoint, bucketName, obj, filename string) (url string, err error) {
	client, err := NewOss(endpoint)
	// 获取存储空间。
	bucket, err := client.Bucket(bucketName)
	if err != nil {
		return
	}
	bucket.SetObjectMeta(obj, oss.ContentDisposition(fmt.Sprintf("attachment; filename=%v", filename)))
	return bucket.SignURL(obj, oss.HTTPGet, 3600)
}

var PrivateExt = ",ts,"

func Listfile(maker, endpoint, prefix, bucketName string, num int) (data oss.ListObjectsResult, err error) {
	// Create bucket
	// New client
	client, err := NewOss(endpoint)
	if err != nil {
		return data, err
	}
	// Create bucket
	err = client.CreateBucket(bucketName)
	if err != nil {
		return data, err
	}

	// Get bucket
	bucket, err := client.Bucket(bucketName)
	if err != nil {
		return data, err
	}
	if err != nil {
		return data, err
	}
	marker := oss.Marker(maker)
	data, err = bucket.ListObjects(oss.MaxKeys(num), marker, oss.Prefix(prefix))
	if err != nil {
		return data, err
	}

	return data, err
}

// uploadPath 路径 eg: teams-config/projectDboApi/test/test.txt
// reqDataMapByte 文件内容
// downloadUrl 下载地址 eg: https://mutifile-common.oss-cn-zhangjiakou.aliyuncs.com/teams-config/projectDboApi/test/test.txt
func UploadFileConmtent2Oss(uploadPath string, reqDataMapByte string) (downloadUrl string, err error) {

	// Endpoint以杭州为例，其它Region请按实际情况填写。
	// 阿里云主账号AccessKey拥有所有API的访问权限，风险很高。强烈建议您创建并使用RAM账号进行API访问或日常运维，请登录 https://ram.console.aliyun.com 创建RAM账号。

	// 创建OSSClient实例。
	client, err := oss.New(config.C.Oss.PublicEndPoint, config.C.Oss.AccessKeyId, config.C.Oss.AccessKeySecret)
	if err != nil {
		return "", err
	}
	// 获取存储空间。
	bucket, err := client.Bucket(config.C.Oss.PublicBucket)
	if err != nil {
		return "", err
	}

	// 上传文件流。
	err = bucket.PutObject(uploadPath, strings.NewReader(reqDataMapByte), oss.Progress(&OssProgressListener{}))
	if err != nil {
		return "", err
	}

	return "https://" + config.C.Oss.PublicBucket + "." + config.C.Oss.PublicEndPoint + "/" + uploadPath, nil
}
